package mylittleinvestigations;

import java.util.logging.Level;
import java.util.logging.Logger;
import mylittleinvestigations.PersistentThreads.RenderThread;
import mylittleinvestigations.CaseInformation.CaseXmlWriter;
import mylittleinvestigations.CaseInformation.Case;
import mylittleinvestigations.CaseInformation.CaseXmlReader;
import java.awt.image.BufferedImage;
import java.awt.image.RasterFormatException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import mylittleinvestigations.CaseInformation.CaseParseException;
import org.newdawn.slick.*;
import org.newdawn.slick.geom.*;

public class Sprite
{
    private String spriteSheetTextureId;
    private Image spriteSheetTexture;
    private Rectangle spriteClipRect;

    private String imageFilePath;
    private BufferedImage bufferedImage;
    private boolean bufferedImageIsBlank;
    
    private Vector2 offset;
    private Vector2 originalSize;
    
    private ArrayList<String> locationParents = new ArrayList<String>();
    
    private boolean isUsed;

    public Sprite(String imageFilePath)
    {
        this.imageFilePath = imageFilePath;
        this.isUsed = false;
        
        this.bufferedImageIsBlank = false;
        this.offset = new Vector2(0, 0);
        this.originalSize = new Vector2(0, 0);
    }

    private Image getSpriteSheetTexture()
    {
        if (this.spriteSheetTexture == null)
        {
            this.spriteSheetTexture = Case.getInstance().getSpriteManager().GetTextureFromId(this.spriteSheetTextureId);
        }

        return this.spriteSheetTexture;
    }
    
    public void persistBufferedImage() throws CaseParseException
    {
        if (this.imageFilePath != null)
        {
            java.nio.file.Path path = Paths.get(this.imageFilePath);
            BufferedImage bufferedImageTemp = null;
            
            try
            {
                if (!path.isAbsolute())
                {
                    bufferedImageTemp = ImageIO.read(new File("data/" + this.imageFilePath));
                }
                else
                {
                    bufferedImageTemp = ImageIO.read(path.toFile());
                }
            }
            catch (IOException ex)
            {
                throw new CaseParseException("Image file data/" + this.imageFilePath + " does not exist.", ex);
            }
            
            this.originalSize = new Vector2(bufferedImageTemp.getWidth(), bufferedImageTemp.getHeight());
            
            int left;
            int top;
            int right;
            int bottom;
            
            for (left = 0; left < bufferedImageTemp.getWidth(); left++)
            {
                boolean columnWasBlank = true;
                
                for (int y = 0; y < bufferedImageTemp.getHeight(); y++)
                {
                    int pixelRgb = bufferedImageTemp.getRGB(left, y);
                    int alpha = (pixelRgb & 0xff000000) >> 24;
                    
                    if (alpha != 0)
                    {
                        columnWasBlank = false;
                        break;
                    }
                }
                
                if (!columnWasBlank)
                {
                    break;
                }
            }
            
            for (right = bufferedImageTemp.getWidth(); right > 0; right--)
            {
                boolean columnWasBlank = true;
                
                for (int y = 0; y < bufferedImageTemp.getHeight(); y++)
                {
                    int pixelRgb = bufferedImageTemp.getRGB(right - 1, y);
                    int alpha = (pixelRgb & 0xff000000) >> 24;
                    
                    if (alpha != 0)
                    {
                        columnWasBlank = false;
                        break;
                    }
                }
                
                if (!columnWasBlank)
                {
                    break;
                }
            }
            
            for (top = 0; top < bufferedImageTemp.getHeight(); top++)
            {
                boolean rowWasBlank = true;
                
                for (int x = 0; x < bufferedImageTemp.getWidth(); x++)
                {
                    int pixelRgb = bufferedImageTemp.getRGB(x, top);
                    int alpha = (pixelRgb & 0xff000000) >> 24;
                    
                    if (alpha != 0)
                    {
                        rowWasBlank = false;
                        break;
                    }
                }
                
                if (!rowWasBlank)
                {
                    break;
                }
            }
            
            for (bottom = bufferedImageTemp.getHeight(); bottom > 0; bottom--)
            {
                boolean rowWasBlank = true;
                
                for (int x = 0; x < bufferedImageTemp.getWidth(); x++)
                {
                    int pixelRgb = bufferedImageTemp.getRGB(x, bottom - 1);
                    int alpha = (pixelRgb & 0xff000000) >> 24;
                    
                    if (alpha != 0)
                    {
                        rowWasBlank = false;
                        break;
                    }
                }
                
                if (!rowWasBlank)
                {
                    break;
                }
            }
            
            if (left >= right || top >= bottom)
            {
                this.bufferedImageIsBlank = true;
            }
            else
            {
                this.bufferedImage = bufferedImageTemp.getSubimage(left, top, right - left, bottom - top);
                this.offset = new Vector2(left, top);
            }
        }
    }

    public void disposeBufferedImage()
    {
        this.bufferedImage = null;
    }
    
    public BufferedImage getBufferedImage() throws CaseParseException
    {
        if (!this.bufferedImageIsBlank && this.bufferedImage == null)
        {
            this.persistBufferedImage();
        }
        
        return this.bufferedImage;
    }

    public String getSpriteSheetTextureId()
    {
        return this.spriteSheetTextureId;
    }

    public Rectangle getSpriteClipRect()
    {
        return this.spriteClipRect;
    }

    public float getWidth()
    {
        try
        {
            return this.getBufferedImage() != null ? this.getBufferedImage().getWidth() : (this.spriteClipRect != null ? this.spriteClipRect.getWidth() : 0);
        }
        catch (CaseParseException ex)
        {
            return 0;
        }
    }

    public float getHeight()
    {
        try
        {
            return this.getBufferedImage() != null ? this.getBufferedImage().getHeight() : (this.spriteClipRect != null ? this.spriteClipRect.getHeight() : 0);
        }
        catch (CaseParseException ex)
        {
            return 0;
        }
    }

    public double getOriginalWidth()
    {
        return this.originalSize.getX();
    }

    public double getOriginalHeight()
    {
        return this.originalSize.getY();
    }
    
    public boolean getIsUsed()
    {
        return this.isUsed;
    }

    public void LoadSpriteSheet(String spriteSheetTextureId, Rectangle spriteClipRect)
    {
        this.spriteSheetTextureId = spriteSheetTextureId;
        this.spriteClipRect = spriteClipRect;
    }

    public void Draw(GameContainer gc, Graphics g, Vector2 position)
    {
        this.Draw(gc, g, position, Color.white, 1.0f /* scale */, false /* flipHorizontally */);
    }

    public void Draw(GameContainer gc, Graphics g, Vector2 position, Color color)
    {
        this.Draw(gc, g, position, color, 1.0f /* scale */, false /* flipHorizontally */);
    }

    public void Draw(GameContainer gc, Graphics g, Vector2 position, Color color, float scale, boolean flipHorizontally)
    {
        Vector2 pixelSnappedPosition = new Vector2((int)position.getX(), (int)position.getY());

        if (this.spriteClipRect != null)
        {
            RenderThread.getInstance().AddImageToRenderQueue(
                    this.getSpriteSheetTexture(),
                    (float)pixelSnappedPosition.getX(),
                    (float)pixelSnappedPosition.getY(),
                    (int)this.spriteClipRect.getX(),
                    (int)this.spriteClipRect.getY(),
                    (int)this.spriteClipRect.getWidth(),
                    (int)this.spriteClipRect.getHeight(),
                    flipHorizontally,
                    false /* flipVertically */,
                    scale,
                    color);
        }
    }

    public void DrawClipped(GameContainer gc, Graphics g, Vector2 position, Rectangle clipRect)
    {
        Vector2 pixelSnappedPosition = new Vector2((int)position.getX(), (int)position.getY());

        if (this.spriteClipRect != null)
        {
            RenderThread.getInstance().AddImageToRenderQueue(
                    this.getSpriteSheetTexture(),
                    (float)pixelSnappedPosition.getX(),
                    (float)pixelSnappedPosition.getY(),
                    (int)(this.spriteClipRect.getX() + clipRect.getX()),
                    (int)(this.spriteClipRect.getY() + clipRect.getY()),
                    (int)clipRect.getWidth(),
                    (int)clipRect.getHeight(),
                    false /* flipHorizontally */,
                    false /* flipVertically */,
                    1.0f,
                    Color.white);
        }
    }

    public void SaveToXml(CaseXmlWriter writer) throws IOException
    {
        writer.StartElement("Sprite");
        
        if (!this.bufferedImageIsBlank)
        {
            writer.WriteTextElement("SpriteSheetImageId", this.spriteSheetTextureId);

            writer.StartElement("SpriteClipRect");
            writer.WriteRectangleElement(this.spriteClipRect);
            writer.EndElement();

            writer.StartElement("Offset");
            this.offset.SaveToXml(writer);
            writer.EndElement();
            
            writer.StartElement("OriginalSize");
            this.originalSize.SaveToXml(writer);
            writer.EndElement();
        }

        writer.EndElement();

        if (!this.bufferedImageIsBlank)
        {
            for (String locationParent : this.locationParents)
            {
                Case.getInstance().AddParentLocationForSpriteSheetId(spriteSheetTextureId, locationParent);
            }
        }
    }

    public Sprite(CaseXmlReader reader)
    {
        reader.StartElement("Sprite");
        this.spriteSheetTextureId = reader.ReadTextElement("SpriteSheetImageId");
        
        reader.StartElement("SpriteClipRect");
        this.spriteClipRect = reader.ReadRectangleElement();
        reader.EndElement();
        
        reader.EndElement();
    }

    public void markChildSprites(String markParent)
    {
        this.isUsed = true;
        
        if (!this.locationParents.contains(markParent))
        {
            this.locationParents.add(markParent);
        }
    }
}